// Copyright (c) Duende Software. All rights reserved.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.

using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text.Json;
using Duende.IdentityModel;
using Duende.IdentityModel.Client;
using Duende.IdentityModel.OidcClient;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using Serilog;
using Serilog.Sinks.SystemConsole.Themes;

namespace ConsoleClientWithBrowser;

public class Program
{
    static string _api = "https://demo.duendesoftware.com/api/test";

    static HttpClient _apiClient = new HttpClient { BaseAddress = new Uri(_api) };

    public static async Task Main()
    {
        Console.WriteLine("+-----------------------+");
        Console.WriteLine("|  Sign in with OIDC    |");
        Console.WriteLine("+-----------------------+");
        Console.WriteLine("");
        Console.WriteLine("Press any key to sign in...");
        Console.ReadKey();

        await SignIn();
    }

    private static string rsaKey =
    "{" +
        "\"d\":\"GmiaucNIzdvsEzGjZjd43SDToy1pz-Ph-shsOUXXh-dsYNGftITGerp8bO1iryXh_zUEo8oDK3r1y4klTonQ6bLsWw4ogjLPmL3yiqsoSjJa1G2Ymh_RY_sFZLLXAcrmpbzdWIAkgkHSZTaliL6g57vA7gxvd8L4s82wgGer_JmURI0ECbaCg98JVS0Srtf9GeTRHoX4foLWKc1Vq6NHthzqRMLZe-aRBNU9IMvXNd7kCcIbHCM3GTD_8cFj135nBPP2HOgC_ZXI1txsEf-djqJj8W5vaM7ViKU28IDv1gZGH3CatoysYx6jv1XJVvb2PH8RbFKbJmeyUm3Wvo-rgQ\"," +
        "\"dp\":\"YNjVBTCIwZD65WCht5ve06vnBLP_Po1NtL_4lkholmPzJ5jbLYBU8f5foNp8DVJBdFQW7wcLmx85-NC5Pl1ZeyA-Ecbw4fDraa5Z4wUKlF0LT6VV79rfOF19y8kwf6MigyrDqMLcH_CRnRGg5NfDsijlZXffINGuxg6wWzhiqqE\"," +
        "\"dq\":\"LfMDQbvTFNngkZjKkN2CBh5_MBG6Yrmfy4kWA8IC2HQqID5FtreiY2MTAwoDcoINfh3S5CItpuq94tlB2t-VUv8wunhbngHiB5xUprwGAAnwJ3DL39D2m43i_3YP-UO1TgZQUAOh7Jrd4foatpatTvBtY3F1DrCrUKE5Kkn770M\"," +
        "\"e\":\"AQAB\"," +
        "\"kid\":\"ZzAjSnraU3bkWGnnAqLapYGpTyNfLbjbzgAPbbW2GEA\"," +
        "\"kty\":\"RSA\"," +
        "\"n\":\"wWwQFtSzeRjjerpEM5Rmqz_DsNaZ9S1Bw6UbZkDLowuuTCjBWUax0vBMMxdy6XjEEK4Oq9lKMvx9JzjmeJf1knoqSNrox3Ka0rnxXpNAz6sATvme8p9mTXyp0cX4lF4U2J54xa2_S9NF5QWvpXvBeC4GAJx7QaSw4zrUkrc6XyaAiFnLhQEwKJCwUw4NOqIuYvYp_IXhw-5Ti_icDlZS-282PcccnBeOcX7vc21pozibIdmZJKqXNsL1Ibx5Nkx1F1jLnekJAmdaACDjYRLL_6n3W4wUp19UvzB1lGtXcJKLLkqB6YDiZNu16OSiSprfmrRXvYmvD8m6Fnl5aetgKw\"," +
        "\"p\":\"7enorp9Pm9XSHaCvQyENcvdU99WCPbnp8vc0KnY_0g9UdX4ZDH07JwKu6DQEwfmUA1qspC-e_KFWTl3x0-I2eJRnHjLOoLrTjrVSBRhBMGEH5PvtZTTThnIY2LReH-6EhceGvcsJ_MhNDUEZLykiH1OnKhmRuvSdhi8oiETqtPE\"," +
        "\"q\":\"0CBLGi_kRPLqI8yfVkpBbA9zkCAshgrWWn9hsq6a7Zl2LcLaLBRUxH0q1jWnXgeJh9o5v8sYGXwhbrmuypw7kJ0uA3OgEzSsNvX5Ay3R9sNel-3Mqm8Me5OfWWvmTEBOci8RwHstdR-7b9ZT13jk-dsZI7OlV_uBja1ny9Nz9ts\"," +
        "\"qi\":\"pG6J4dcUDrDndMxa-ee1yG4KjZqqyCQcmPAfqklI2LmnpRIjcK78scclvpboI3JQyg6RCEKVMwAhVtQM6cBcIO3JrHgqeYDblp5wXHjto70HVW6Z8kBruNx1AH9E8LzNvSRL-JVTFzBkJuNgzKQfD0G77tQRgJ-Ri7qu3_9o1M4\"" +
    "}";

    private static string CreateClientToken(SigningCredentials credential, string clientId, string audience)
    {
        var now = DateTime.UtcNow;

        var token = new JwtSecurityToken(
            clientId,
            audience,
            new List<Claim>()
            {
                new Claim(JwtClaimTypes.JwtId, Guid.NewGuid().ToString()),
                new Claim(JwtClaimTypes.Subject, clientId),
                new Claim(JwtClaimTypes.IssuedAt, now.ToEpochTime().ToString(), ClaimValueTypes.Integer64)
            },
            now,
            now.AddMinutes(1),
            credential
        );

        var tokenHandler = new JwtSecurityTokenHandler();
        return tokenHandler.WriteToken(token);
    }

    private static async Task SignIn()
    {
        // create a redirect URI using an available port on the loopback address.
        // requires the OP to allow random ports on 127.0.0.1 - otherwise set a static port
        var browser = new SystemBrowser();
        var redirectUri = string.Format($"http://127.0.0.1:{browser.Port}");
        var authority = "https://demo.duendesoftware.com";

        var jwk = new JsonWebKey(rsaKey);
        var credential = new SigningCredentials(jwk, "RS256");

        var options = new OidcClientOptions
        {
            Authority = authority,
            ClientId = "interactive.confidential.short.jwt",
            GetClientAssertionAsync = () => Task.FromResult(new ClientAssertion
            {
                Type = OidcConstants.ClientAssertionTypes.JwtBearer,
                Value = CreateClientToken(credential, "interactive.confidential.short.jwt", authority)
            }),
            RedirectUri = redirectUri,
            Scope = "openid profile api offline_access",
            FilterClaims = false,

            Browser = browser,
            RefreshTokenInnerHttpHandler = new SocketsHttpHandler(),
        };

        var serilog = new LoggerConfiguration()
            .MinimumLevel.Debug()
            .Enrich.FromLogContext()
            .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message}{NewLine}{Exception}{NewLine}", theme: AnsiConsoleTheme.Code)
            .CreateLogger();

        options.LoggerFactory = new LoggerFactory().AddSerilog(serilog);

        var oidcClient = new OidcClient(options);
        var result = await oidcClient.LoginAsync(new LoginRequest());

        _apiClient = new HttpClient(result.RefreshTokenHandler)
        {
            BaseAddress = new Uri(_api)
        };

        ShowResult(result);
        await NextSteps(result);
    }

    private static void ShowResult(LoginResult result)
    {
        if (result.IsError)
        {
            Console.WriteLine("\n\nError:\n{0}", result.Error);
            return;
        }

        Console.WriteLine("\n\nClaims:");
        foreach (var claim in result.User.Claims)
        {
            Console.WriteLine("{0}: {1}", claim.Type, claim.Value);
        }

        var raw = result.TokenResponse.Raw ?? throw new InvalidOperationException("unexpected null token response");
        var values = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(raw);

        Console.WriteLine($"token response...");
        foreach (var item in values ?? [])
        {
            Console.WriteLine($"{item.Key}: {item.Value}");
        }
    }

    private static async Task NextSteps(LoginResult result)
    {
        var menu = "  x...exit  c...call api   ";

        while (true)
        {
            Console.WriteLine("\n\n");

            Console.Write(menu);
            var key = Console.ReadKey();

            if (key.Key == ConsoleKey.X)
            {
                return;
            }

            if (key.Key == ConsoleKey.C)
            {
                await CallApi();
            }
        }
    }

    private static async Task CallApi()
    {
        var response = await _apiClient.GetAsync("");

        if (response.IsSuccessStatusCode)
        {
            var json = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
            Console.WriteLine("\n\n");
            Console.WriteLine(json.RootElement);
        }
        else
        {
            Console.WriteLine($"Error: {response.ReasonPhrase}");
        }
    }
}
